/*
		ServEngine framework library
		A library to fast develop JEE online applications
	
		Copyright 2008 José Ignacio de Córdoba Álvaro

		Licensed under the Apache License, Version 2.0 (the "License");
		you may not use this file except in compliance with the License.
		You may obtain a copy of the License at

				http://www.apache.org/licenses/LICENSE-2.0

		Unless required by applicable law or agreed to in writing, software
		distributed under the License is distributed on an "AS IS" BASIS,
		WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
		See the License for the specific language governing permissions and
		limitations under the License.
	
		Jose Ignacio de Cordoba Alvaro
		http://ignacio.decordoba.com
*/

package com.servengine.user;

import com.servengine.entry.Entry;

import java.io.Serializable;

import com.servengine.portal.Portal;

import java.util.ArrayList;
import java.util.Date;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;

@Entity
@NamedQueries({ @NamedQuery(name="User.findByUserid", query="select o from User o where o.portal.id = :portalid and o.userid = :userid")
		, @NamedQuery(name="User.findByPortalid", query="select o from User o where o.portal.id = :portalid")
		, @NamedQuery(name="User.findByPortalidOrderByCreatedDesc", query="select o from User o where o.portal.id = :portalid order by o.created desc")
		, @NamedQuery(name="User.findAdminUsers", query="select o from User o where o.portal.id = :portalid and o.userid = 'guest'")
		, @NamedQuery(name="User.findGuestUser", query="select o from User o where o.portal.id = :portalid and o.userid = 'guest'")
		, @NamedQuery(name="User.findByEmail", query="select u from User u where u.email = :email")
		, @NamedQuery(name="User.findNew", query="select u from User u where u.portal.id = :portalid and u.created >= :from")
		, @NamedQuery(name="User.findAll", query="select o from User o")
		, @NamedQuery(name="User.getUserCount", query="select count(o) from User o")
		} )
@Table(uniqueConstraints={ @UniqueConstraint(columnNames={ "userid", "portal_id" })
			} )
public class User implements Serializable, Comparable<User> //,javax.servlet.http.HttpSessionBindingListener
{
	public static String LOCALE_PROPERTY_NAME="locale";
	protected static final String encodepassword="123";
	protected static org.apache.commons.logging.Log log=org.apache.commons.logging.LogFactory.getLog(User.class.getName());

	protected Integer id;
	protected String userid, firstname, lastname, lastname2, email, password; //La password se almacena cifrada aquí.
	protected Date created, lastLogin;

	private Portal portal;
	protected Set<Role> roles;
	protected Set<ExtraUserdata> extraUserdata;
	protected Set<UserProperty> properties;
	protected Set<Entry> entries;

	protected Set<String> actionClassNames;
	protected byte[] picture;

	Set<User> following, followedBy, friends;

	public User()
	{
		created=new java.util.Date();
		actionClassNames=new HashSet<String>(); //TODO Comentar cuando se sepa que no casca (NullPointer)
		properties=new HashSet<UserProperty>();
		extraUserdata=new HashSet<ExtraUserdata>();
		roles=new HashSet<Role>();
	}

	public User(Portal portal)
	{
		this();
		if (portal==null)
			throw new IllegalArgumentException();
		this.portal=portal;
	}

	public User(Portal portal, String userid)
	{
		this(portal);
		if (userid==null||userid.length()==0)
			throw new IllegalArgumentException();
		this.userid=userid==null?null:userid.toLowerCase();
	}

	public int compareTo(User o)
	{
		int i=userid.compareTo(o.getUserid());
		return i==0?portal.compareTo(o.getPortal()):i;
	}

	public boolean equals(Object item)
	{
		if (id==null)
			return ((User)item).getId()==null && portal.equals(((User)item).getPortal()) && userid.equals(((User)item).getUserid());
		return id.equals(((User)item).getId());
	}

	public int hashCode()
	{
		return id!=null?new org.apache.commons.lang.builder.HashCodeBuilder(-354333345, -1492094871).append(id).toHashCode():new org.apache.commons.lang.builder.HashCodeBuilder(-354333345, -1492094871).append(portal.getId()).append(userid).toHashCode();
	}

	@Transient
	public String getPortalid()
	{
		return portal.getId();
	}

	@ManyToOne(fetch=FetchType.EAGER, optional=false)
	public Portal getPortal()
	{
		return portal;
	}

	public void setPortal(Portal portal)
	{
		this.portal=portal;
	}

	public String getUserid()
	{
		return userid;
	}

	public String getPassword()
	{
		return password;
	}

	@Transient
	public String getName()
	{
		return (firstname==null?"":firstname)+(lastname==null?"":" "+lastname)+(lastname2==null?"":" "+lastname2);
	}

	public String getFirstname()
	{
		return firstname;
	}

	public String getLastname()
	{
		return lastname;
	}

	public String getLastname2()
	{
		return lastname2;
	}

	public String getEmail()
	{
		return email;
	}

	@Id
	@GeneratedValue
	public Integer getId()
	{
		return id;
	}

	public Date getCreated()
	{
		return created;
	}

	public Date getLastLogin()
	{
		return lastLogin;
	}

	@ManyToMany(targetEntity=Role.class, fetch=FetchType.EAGER)
	public Set<Role> getRoles()
	{
		return roles;
	}

	public void setRoles(Set<Role> roles)
	{
		this.roles=roles;
	}

	@Transient
	public List<Role> getRoleList()
	{
		ArrayList<Role> roleList=new ArrayList<Role>();
		roleList.addAll(roles);
		return roleList;
	}

	public void setFirstname(String value)
	{
		firstname=value;
	}

	public void setLastname(String value)
	{
		lastname=value;
	}

	public void setLastname2(String value)
	{
		lastname2=value;
	}

	public void setEmail(String value)
	{
		email=value;
	}

	@OneToMany(mappedBy="user", fetch=FetchType.EAGER, cascade={ CascadeType.REMOVE })
	public Set<ExtraUserdata> getExtraUserdata()
	{
		return extraUserdata;
	}

	public void setExtraUserdata(Set<ExtraUserdata> values)
	{
		extraUserdata=values;
	}

	@Transient
	public Map<String, String> getExtraUserdataMap()
	{
		Map<String, String> map=new HashMap<String, String>();
		for (ExtraUserdata datum: extraUserdata)
			map.put(datum.getField().getName(), datum.getValue());
		return map;
	}

	@Transient
	public Integer[] getRoleIds()
	{
		Integer[] roleIds=new Integer[roles.size()];
		int i=0;
		for (Role role: roles)
			roleIds[i++]=role.getId();
		return roleIds;
	}

	@Transient
	public boolean isGuest()
	{
		return userid==null||portal.getGuestUserid().equals(userid);
	}

	@Transient
	public boolean isAdmin()
	{
		for (Role role: roles)
			if (role.getName().equals(portal.getAdminRoleName()))
				return true;
		return false;
	}

	@Transient
	public boolean isHasPicture()
	{
		return picture!=null;
	}

	public boolean checkEncodedPassword(String value)
	{
		if ((value==null||value.equals(""))&&(getPassword()==null||getPassword().equals("")))
			return true;
		if (getPassword()==null||value==null)
			return false;
		String encoded=User.getEncodedPassword(getPortal().getId(), getUserid(), value);
		return encoded.equals(getPassword());
	}

	@Transient
	public static String getEncodedPassword(String portalid, String userid, String value)
	{
		if (value==null)
			return null;
		String[] values=new String[4];
		values[0]=portalid;
		values[1]=userid;
		values[2]=value;
		values[3]=portalid+userid+encodepassword;
		return UserManagerBean.getMessageDigest(values);
	}

	public void encodePassword(String password)
	{
		setPassword(getEncodedPassword(portal.getId(), userid, password));
	}

	public void setLastLogin(Date lastLogin)
	{
		this.lastLogin=lastLogin;
	}

	public void setUserid(String id)
	{
		this.userid=id;
	}

	public void setPassword(String password)
	{
		this.password=password;
	}

	@Id
	@GeneratedValue
	public void setId(Integer id)
	{
		this.id=id;
	}

	public void setCreated(Date created)
	{
		this.created=created;
	}

	@Transient
	public Set<String> getActionClassNames()
	{
		return actionClassNames;
	}

	@Transient
	public Integer getIntegerProperty(String portalid, String propertyid)
	{
		String property=getProperty(propertyid);
		try
		{
			if (property!=null)
				return new Integer(property);
		} catch (NumberFormatException e)
		{
			if (log.isDebugEnabled())
				log.debug(property+" for "+propertyid+"@"+portalid+" can't be used as an Integer. Returning null");
			return null;
		}
		return null;
	}

	@Transient
	public int getIntproperty(String portalid, String propertyid)
	{
		String property=getProperty(propertyid);
		try
		{
			if (property!=null)
				return Integer.parseInt(property);
		} catch (NumberFormatException e)
		{
			if (log.isDebugEnabled())
				log.debug(property+" for "+propertyid+"@"+portalid+" can't be used as an Integer. Returning null");
			return 0;
		}
		return 0;
	}

	public void setProperties(Set<UserProperty> properties)
	{
		this.properties=properties;
	}

	@OneToMany(mappedBy="user", fetch=FetchType.EAGER, cascade={ CascadeType.ALL })
	public //TODO Pasar a Lazy
	Set<UserProperty> getProperties()
	{
		return properties;
	}

	@Transient
	public void setProperty(String name, String value)
	{
		if (value==null||value.length()==0)
		{
			removeProperty(name);
			return;
		}
		for (UserProperty property: properties)
			if (property.getName().equals(name))
			{
				property.setValue(value);
				return;
			}
		properties.add(new UserProperty(this, name, value));
	}

	@Transient
	public String getProperty(String name)
	{
		if (properties!=null)
			for (UserProperty property: properties)
				if (property.getName().equals(name))
					return property.getValue();
		return null;
	}

	@Transient
	public boolean getBooleanProperty(String propertyid)
	{
		try
		{
			String property=getProperty(propertyid);
			return property!=null&&new Boolean(property).booleanValue();
		} catch (Throwable t)
		{
			return false;
		}
	}

	@Transient
	public Map<String, String> getPropertyMap()
	{
		Map<String, String> map=new HashMap<String, String>();
		for (UserProperty property: properties)
			map.put(property.getName(), property.getValue());
		return map;
	}

	public void removeProperty(String name)
	{
		Set<UserProperty> newProperties=new HashSet<UserProperty>();
		for (UserProperty property: properties)
			if (!property.getName().equals(name))
				newProperties.add(property);
		properties=newProperties;
	}

	@Transient
	public String getLocale()
	{
		return getProperty(LOCALE_PROPERTY_NAME);
	}

	public void setLocale(String value)
	{
		setProperty(LOCALE_PROPERTY_NAME, value);
	}

	public void setActionClassNames(Set<String> actionClassNames)
	{
		this.actionClassNames=actionClassNames;
	}

	public void setPicture(byte[] picture)
	{
		this.picture=picture;
	}

	@Lob
	@Basic(fetch=FetchType.EAGER)
	public //FIXME Ver qué hacemos con esto. Necesario para el blog por ejemplo.
	byte[] getPicture()
	{
		return picture;
	}

	public void setFollowing(Set<User> following)
	{
		this.following=following;
	}

	@ManyToMany(fetch=FetchType.LAZY)
	public Set<User> getFollowing()
	{
		return following;
	}

	/*  @Transient // No tiene utilidad al ser Lazy
	@Transient
	public Map<String,User> getFollowingMap()
	{
		Map<String,User> map = new HashMap<String,User>();
		for (User user: following)
			map.put(user.getUserid(),user);
		return map;
	}*/

	public void setFollowedBy(Set<User> followedBy)
	{
		this.followedBy=followedBy;
	}

	@ManyToMany(fetch=FetchType.LAZY, mappedBy="following")
	public Set<User> getFollowedBy()
	{
		return followedBy;
	}

	public void setFriends(Set<User> friends)
	{
		this.friends=friends;
	}

	@ManyToMany(fetch=FetchType.LAZY)
	public Set<User> getFriends()
	{
		return friends;
	}

	public void setEntries(Set<Entry> entries)
	{
		this.entries=entries;
	}

	@OneToMany(mappedBy="user", fetch=FetchType.LAZY, cascade={ CascadeType.REMOVE })
	public Set<Entry> getEntries()
	{
		return entries;
	}
}
